Skip to content

[MOD-109][Feature] Reviews, Volume I: Moderation#7708

Closed
aaxelb wants to merge 49 commits into
CenterForOpenScience:developfrom
aaxelb:feature/reviews
Closed

[MOD-109][Feature] Reviews, Volume I: Moderation#7708
aaxelb wants to merge 49 commits into
CenterForOpenScience:developfrom
aaxelb:feature/reviews

Conversation

@aaxelb

@aaxelb aaxelb commented Sep 14, 2017

Copy link
Copy Markdown
Collaborator

https://openscience.atlassian.net/browse/MOD-109

Purpose

All the backend changes required for OSF Reviews. Adds workflows for moderators to control which preprints are publicly available in their branded interface.

Changes

  • Add Action top-level object
    • Each action represents a transition, a state change on an object
    • e.g. "Abram submitted preprint X for review at time Y, changing its state from initial to pending"
    • Once created, actions are meant to be immutable
    • New API endpoints:
      • /actions/
        • Write-only endpoint for creating actions
        • GET returns an empty list for the sake of the browsable API
      • /actions/<id>/
        • Read-only action detail
      • /preprints/<guid>/actions/
        • Read-only list of actions which target the given preprint
      • /users/me/actions/
        • Read-only list of actions from providers for which the user is a moderator
  • Add ReviewableMixin for objects which can be reviewed
    • Right now just PreprintService
    • Fields:
      • reviews_state: Current state of the preprint
    • Includes methods to trigger transitions and create corresponding Actions
  • Add ReviewProviderMixin for objects which own Reviewables and can choose a workflow for them
    • Right now just PreprintProvider
    • Fields:
      • reviews_workflow: Provider's chosen workflow (see below)
      • reviews_comments_private: Whether comments made by moderators are visible to the submitter/contributors
      • reviews_comments_anonymous: Whether the name/ID of the moderators who accept/reject/act on a preprint are visible to the submitter/contributors
    • Modified API endpoints:
      • /preprint_providers/<id>/ is once-writable
        • Provider admins can set the three reviews fields exactly once to set up reviews
        • If reviews_workflow is non-null, the endpoint is read-only
  • Add workflows that define valid transitions
    • Workflows:
      • pre-moderation: Preprints are publicly visible as soon as they're submitted, but can later be removed by moderators
      • post-moderation: Preprints are visible only after approval by a moderator
    • Valid states:
      • initial: Default state, created but not yet submitted
      • pending: Submitted for and awaiting review, may be publicly visible depending on the workflow
      • accepted: Has explicit moderator approval, publicly visible under the provider
      • rejected: Has explicit moderator disapproval, only visible to contributors and moderators
    • Valid action triggers
      • submit
        • Move from inital to pending, or from rejected to pending if resubmission is allowed by the workflow
        • Node admins and moderators can submit
      • accept
        • Move from pending or rejected to accepted
        • Only moderators can accept
      • reject
        • Move from pending or accepted to rejected
        • Only moderators can reject
      • edit_comment
        • Modify the comment visible to moderators (and possibly contributors) without changing the preprint's state
        • Only moderators can edit_comment
  • Use django guardian for permissions
    • Create admin and moderator groups for each provider, assign group various object permissions on that provider
    • Add users to groups to make them admin and/or moderator

Side Effects

Preprints workflow

If a provider has non-null reviews_workflow, authors can no longer publish their preprints directly (by setting is_published). Instead, they must create a submit action at /actions/. The corresponding changes to ember-preprints are here: CenterForOpenScience/ember-osf-preprints#454

If a provider has null reviews_workflow, the existing behavior is unchanged. Workflows are entirely opt-in per provider, for now.

Ember OSF

ember-osf changes: CenterForOpenScience/ember-osf#272

@aaxelb
aaxelb force-pushed the feature/reviews branch 2 times, most recently from 9f21d66 to f4787fb Compare September 14, 2017 21:12
@aaxelb aaxelb changed the title [WIP] Reviews, Volume I: Moderation [WIP][Feature] Reviews, Volume I: Moderation Sep 15, 2017
@aaxelb aaxelb changed the title [WIP][Feature] Reviews, Volume I: Moderation [Feature] Reviews, Volume I: Moderation Sep 15, 2017
Comment thread docker-compose.override.yml Outdated
## command: /bin/bash -c "cd /ember-osf && yarn link && cd /code && yarn link @centerforopenscience/ember-osf && yarn --pure-lockfile --ignore-engines && ./node_modules/bower/bin/bower install --allow-root --config.interactive=false && ./node_modules/ember-cli/bin/ember serve --host 0.0.0.0 --port 4300"

reviews:
#build: ../ember-osf-reviews # remove me before committing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this block needs to be commented out. Also, assuming this comment is correct, this line should be removed.

Comment thread docker-sync.yml Outdated
# sync_excludes: ['.DS_Store', '*.map', '*.pyc', '*.tmp', '.git', '.idea', 'bower_components', 'node_modules', 'tmp', 'dist']
# watch_excludes: ['.*\.DS_Store', '.*\.map', '.*\.pyc', '.*\.tmp', '.*/\.git', '.*/\.idea', '.*/bower_components', '.*/node_modules', '.*/tmp', '.*/dist']

reviews-sync:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: comment out?

Comment thread framework/auth/oauth_scopes.py Outdated
# Full permissions: all routes intended to be exposed to third party API users
FULL_READ = NODE_ALL_READ + USERS_READ + ORGANIZER_READ + GUIDS_READ + METASCHEMAS_READ + DRAFT_READ + (CoreScopes.INSTITUTION_READ, CoreScopes.SEARCH, )
FULL_READ = NODE_ALL_READ + USERS_READ + ORGANIZER_READ + GUIDS_READ + METASCHEMAS_READ + DRAFT_READ + REVIEWS_READ + (CoreScopes.INSTITUTION_READ, CoreScopes.SEARCH, )
FULL_WRITE = FULL_READ + NODE_ALL_WRITE + USERS_WRITE + ORGANIZER_WRITE + DRAFT_WRITE

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should REVIEWS_WRITE be added here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, yeah.

@sloria

sloria commented Sep 15, 2017

Copy link
Copy Markdown
Contributor

I'm getting an error when running manage.py migrate:

Operations to perform:
  Apply all migrations: addons_bitbucket, addons_box, addons_dataverse, addons_dropbox, addons_figshare, addons_forward, addons_github, addons_googledrive, addons_mendeley, addons_osfstorage, addons_owncloud, addons_s3, addons_twofactor, addons_wiki, addons_zotero, admin, auth, contenttypes, guardian, osf, sessions
Running migrations:
  Applying guardian.0001_initial... OK
  Applying osf.0057_order_fileversion_by_date_created... OK
  Applying osf.0058_merge_20170913_2232... OK
  Applying osf.0059_merge_20170914_1100... OK
  Applying osf.0060_reviews...Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/core/management/__init__.py", line 363, in execute_from_command_line
    utility.execute()
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/core/management/__init__.py", line 355, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/core/management/base.py", line 283, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/core/management/base.py", line 330, in execute
    output = self.handle(*args, **options)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 204, in handle
    fake_initial=fake_initial,
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/migrations/executor.py", line 115, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/migrations/executor.py", line 145, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/migrations/executor.py", line 244, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/migrations/migration.py", line 129, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/migrations/operations/special.py", line 193, in database_forwards
    self.code(from_state.apps, schema_editor)
  File "/Users/sloria/projects/cos-projects/osf/osf/migrations/0060_reviews.py", line 16, in create_provider_auth_groups
    GroupHelper(provider).update_provider_auth_groups()
  File "/Users/sloria/projects/cos-projects/osf/reviews/permissions.py", line 88, in update_provider_auth_groups
    assign_perm(p, group, self.provider)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/guardian/shortcuts.py", line 109, in assign_perm
    return model.objects.assign_perm(perm, group, obj)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/guardian/managers.py", line 39, in assign_perm
    permission = Permission.objects.get(content_type=ctype, codename=perm)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/sloria/miniconda/envs/osf/lib/python2.7/site-packages/django/db/models/query.py", line 380, in get
    self.model._meta.object_name
django.contrib.auth.models.DoesNotExist: Permission matching query does not exist.

@sloria

sloria commented Sep 15, 2017

Copy link
Copy Markdown
Contributor

Aha, this seems to fix it

diff --git a/osf/migrations/0060_reviews.py b/osf/migrations/0060_reviews.py
index 8462b26682..b95512c321 100644
--- a/osf/migrations/0060_reviews.py
+++ b/osf/migrations/0060_reviews.py
@@ -3,6 +3,7 @@
 from __future__ import unicode_literals

 from django.conf import settings
+from django.core.management.sql import emit_post_migrate_signal
 from django.db import migrations, models
 import django.db.models.deletion
 import osf.models.base
@@ -11,6 +12,8 @@ from reviews.permissions import GroupHelper


 def create_provider_auth_groups(apps, schema_editor):
+    # this is to make sure that the permissions created in an earlier migration exist!
+    emit_post_migrate_signal(2, False, 'default')
     PreprintProvider = apps.get_model('osf', 'PreprintProvider')
     for provider in PreprintProvider.objects.all():
         GroupHelper(provider).update_provider_auth_groups()

@aaxelb

aaxelb commented Sep 15, 2017

Copy link
Copy Markdown
Collaborator Author

@sloria Nice! Don't know why that didn't break in the same way for me, but I'll fix that up.

@brianjgeiger brianjgeiger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great from my perspective. A few things here and there, but no show-stoppers for me.

Comment thread api/actions/serializers.py Outdated
super(ReviewableCountsRelationshipField, self).__init__(*args, **kwargs)

def get_meta_information(self, metadata, provider):
# Clone metadata because it's mutablity is questionable

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trivial: its not it's.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread api/actions/serializers.py Outdated
return target.reviews_submit(user)
except InvalidTransitionError:
# Invalid transition from the current state
raise JSONAPIAttributeException(attribute='trigger', detail='Cannot trigger "{}" from state "{}"'.format(trigger, target.reviews_state))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a 409 conflict. At a future todo (and for the one below), it would be awesome if it could also tell the user what the valid transitions are from this point.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread api/actions/views.py


class CreateAction(JSONAPIBaseView, generics.ListCreateAPIView):
"""Create Actions *Write-only*

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've discussed, but more important than this is documentation at http://github.com/centerforopenscience/developer.osf.io

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I'll update my WIP PR there next week.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect this API to be stable? If nah, it shouldn't go to developer.osf.io quite yet.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, though it probably shouldn't go here, either. @aaxelb you'll be getting an invite to an osf project which will be a better place to hold this info until it's stable.

Comment thread api/actions/views.py Outdated

trigger = serializer.validated_data['trigger']
permission = reviews_permissions.TRIGGER_PERMISSIONS[trigger]
if permission is not None and not self.request.user.has_perm(permission, target.provider):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does reviews_permissions.ActionPermission not catch this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it depends on the trigger value I put this check after the serializer had validated. Do you think it would be better to have some validation logic in ActionPermission (and raise 400 if need be), or to have some permission logic in the view? I guess the former seems cleaner...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, it's better to have permissions checks in the permissions classes for detail and action views. If not possible, or really messy, then do what you need to do.

Comment thread api/actions/views.py
raise PermissionDenied(detail='Performing trigger "{}" requires permission "{}" on the provider.'.format(trigger, permission))

if not target.provider.is_moderated:
raise Conflict('{} is an unmoderated provider. If you are an admin, set up moderation by setting `reviews_workflow` at {}'.format(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


published = validated_data.pop('is_published', None)
if published and preprint.provider.is_moderated:
raise Conflict('{} uses a moderation workflow, so preprints must be submitted for review instead of published directly. Submit a preprint by creating a `submit` Action at {}'.format(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

def url(self, preprint_provider):
return '/{}preprint_providers/{}/'.format(API_BASE, preprint_provider._id)

def test_update_reviews_settings(self, app, preprint_provider, url, admin, moderator):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a good candidate for a single large test. I approve.

res = self.app.post_json_api(self.url, private_project_payload, auth=self.user.auth)
assert not mock_on_preprint_updated.called

@mock.patch('website.preprints.tasks.get_and_set_preprint_identifiers.si')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific to this PR, but my general comment with mocks is to double-check to make sure that it is actually testing a thing. Since you're getting a 409 below, it probably is, but these are tricky and have sat around doing nothing before, so just in case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from the other tests, looks like it's not actually testing a thing.

assert expected == actual

# order by date_last_transitioned
# TODO once sorting is fixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obvs this should be set to go before you merge.

preprint.save()
bad_payload = self.create_payload(preprint._id, trigger=trigger)
res = app.post_json_api(url, bad_payload, auth=moderator.auth, expect_errors=True)
assert res.status_code == 400

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you fix the item from the top, this will turn into a 409. The request formatting is valid, it just conflicts with an existing database state.

@sloria sloria left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass finished.

PreprintProviderFactory,
)

from tests.base import ApiTestCase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Remove unused imports.

Comment thread api/actions/views.py Outdated
from api.base import permissions as base_permissions


class ActionMixin:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class seems unnecessary. ActionMixin.actions_queryset can be made into a function, something like get_actions_queryset().

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a function originally, but quoth @chrisseto:

I'd vote for a mixin here rather than having a functions just floating around.

But he quit so function it is!

Comment thread api/actions/views.py Outdated
).filter(is_deleted=False)


class ActionDetail(JSONAPIBaseView, generics.RetrieveAPIView, ActionMixin):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to inherit from ActionMixin.

Comment thread reviews/workflow.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from enum import Enum

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is enum34 explicitly declared as a requirement, or is it a sub-dependency? I can't seem to find where it comes from.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just used it and it worked. I'll add it as a requirement.

Comment thread reviews/permissions.py Outdated

if request.method in permissions.SAFE_METHODS:
# Moderators and node contributors can view actions
is_node_contributor = reviewable is not None and reviewable.node.has_permission(auth.user, osf_permissions.READ)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tight coupling with PreprintService (i.e. assuming node is present), but it's probably fine for now.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. I kinda gave up on full decoupling at some point, pending the divorce. Are node permissions going to be generalized to apply to preprints too, or are we switching to something like guardian?

Comment thread scripts/reviews/update_auth_groups.py Outdated
@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Update auth groups for all review providers."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be a management command.

<ul class="dropdown-menu service-dropdown" role="menu">
<li><a data-bind="click: trackClick.bind($data, 'Home')" href="${domain}">OSF<b>HOME</b></a></li>
<li><a data-bind="click: trackClick.bind($data, 'Preprints')" href="${domain}preprints/">OSF<b>PREPRINTS</b></a></li>
<li><a data-bind="click: trackClick.bind($data, 'Registries')" href="${domain}registries/">OSF<b>REGISTRIES</b></a></li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was registries removed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Meant to remove "Reviews".

Comment thread website/preprints/tasks.py Outdated
preprint.node.is_preprint_orphan or
preprint.node.tags.filter(name='qatest').exists() or
preprint.node.is_deleted
not preprint._verified_publishable or

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _verified_publishable is public API, it shouldn't have the leading _.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, _verified_publishable isn't exactly equivalent to the old code; is that expected? It looks like _verified_publishable doesn't check for preprint.node.is_preprint_orphan or preprint.node.is_public.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does check preprint.node.is_preprint, which seems like it covers those?

    @property
    def is_preprint(self):
        # TODO: This is a temporary implementation.
        if not self.preprint_file_id or not self.is_public:
            return False
        if self.preprint_file.node_id == self.id:
            return self.has_published_preprint
        else:
            self._is_preprint_orphan = True
            return False

I think it might be more thorough than the old code? Downside is has_published_preprint makes a redundant query, but I figure it'll have to be reworked in the node/preprint divorce anyway, and it seems worth it to have all the "should this preprint be publicly visible?" logic in one place.

Comment thread api/users/serializers.py Outdated
def get_can_view_reviews(self, obj):
group_qs = GroupObjectPermission.objects.filter(group__user=obj, permission__codename='view_submissions')
user_qs = UserObjectPermission.objects.filter(user=obj, permission__codename='view_submissions')
return group_qs.exists() or user_qs.exists()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Can be slightly optimized with

return group_qs.exists() or obj.userobjectpermission_set.filter(permission__codename='view_submissions')

Comment thread reviews/models/mixins.py Outdated

def get_reviewable_status_counts(self):
assert self.REVIEWABLE_RELATION_NAME, 'REVIEWABLE_RELATION_NAME must be set to compute status counts'
# TODO fix hackery once GUID query set is gone

@sloria sloria Sep 15, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GuidMixinQuerySet has changed dramatically this past week. Is this still an issue?

If the issue is the auto-included GUIDs, then you can remove the include using .include(None).

Something like

base_qs = getattr(self, self.REVIEWABLE_RELATION_NAME)
if isinstance(base_qs, GuidMixinQueryset):
    base_qs = base_qs.include(None)
qs = base_qs.values('....')

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that works nicely, thanks

@aaxelb
aaxelb force-pushed the feature/reviews branch 4 times, most recently from d3ce398 to 0c93b75 Compare September 18, 2017 16:24
@aaxelb

aaxelb commented Sep 18, 2017

Copy link
Copy Markdown
Collaborator Author

@brianjgeiger @sloria Changes made!

@brianjgeiger

Copy link
Copy Markdown
Collaborator

@aaxelb So moving the permissions to the permissions class didn't go well?

@aaxelb

aaxelb commented Sep 18, 2017

Copy link
Copy Markdown
Collaborator Author

@brianjgeiger Not really... It felt wrong to deserialize the response in ActionPermission to get that one value, and separately deserialize in the view. I couldn't find a non-ugly way to use the same deserialized thing in both places. I'm on the fence, though -- having all permission logic together would be way nicer, maybe worth the extra CPU time.

@aaxelb

aaxelb commented Sep 18, 2017

Copy link
Copy Markdown
Collaborator Author

@brianjgeiger I reconsidered -- permission logic in one place is definitely worth deserializing doubly. There might be a better way but this is better than what was.

@sloria sloria left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any remaining blockers.

Comment thread reviews/exceptions.py
@@ -1,2 +1,6 @@
class InvalidTransitionError(Exception):
pass
class InvalidTriggerError(Exception):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread api/actions/views.py

# overrides ListCreateAPIView
def get_queryset(self):
return Action.objects.none()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than returning an empty queryset, would it make more sense to return 405 Method not allowed? @aaxelb @brianjgeiger

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I did that at first (used CreateAPIView instead of ListCreateAPIView), but it hid the browsable API, which felt wrong.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really aren't relying on the browseable API anymore, and once we are able to link to specific sections of the dev docs, all the BAPI docs will just be links. I'm good with this being CreateAPIView.

Comment thread api/base/views.py
embeds = []
else:
embeds = self.request.query_params.getlist('embed')
embeds = self.request.query_params.getlist('embed') or self.request.query_params.getlist('embed[]')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: why was this necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're using jQuery.ajax (as Ember does) and assign an array to a query param, like say {embed: ['target', 'provider']}, it's serialized to ?embed[]=target&embed[]=provider.

The client-side workaround would be turning off deep serialization, but that breaks existing code that uses conveniences like { filter: { parents } } or { page: { size: 10 } }.

Comment thread osf/models/action.py
comment = models.TextField(blank=True)

is_deleted = models.BooleanField(default=False)
date_created = NonNaiveDateTimeField(auto_now_add=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Consider naming these created and modfied for compatibility with #7501

@aaxelb aaxelb changed the title [Feature] Reviews, Volume I: Moderation [MOD-109][Feature] Reviews, Volume I: Moderation Sep 25, 2017
aaxelb and others added 15 commits October 3, 2017 17:30
* State counts

* Restore state counts on provider endpoint

* plac8 flake8
)

* Exclude deleted preprints from counts

* Add state counts test
* Show all preprints on My Projects page

* Use new preprint filter for my projects page

Capitalize status

Don't show status initial
* [MOD-29] Reviews Initial Commit

* [MOD-18] Add reviews to EXTERNAL_EMBER_APPS

* [MOD-20][MOD-21][Feature] Add reviews mixins (CenterForOpenScience#21)

* [MOD-20][Feature] Add PreprintService.state field

* [MOD-21][Feature] Add ReviewProviderMixin

* Responding to review

* Use Enum

* Isolate transitions machine

* comments_public -> comments_private, for clarity

* Log model and read-only endpoints (CenterForOpenScience#24)

* [MOD-12][MOD-17][MOD-39]Setup ember-osf-reviews app (OSF-side) (CenterForOpenScience#22)

* [MOD-12] Setup ember-osf-reviews app
*Update docker setup
*Add osf-reviews campaign
*Add reviews route
*Add osf-reviews logo
*Update registration templates
*Update osf navbar
*Add mako landing page

* fix

* [MOD-22][MOD-24][MOD-25][MOD-48][Feature] Reviews API (CenterForOpenScience#26)

* Add reviews fields to preprint/provider endpoints

* Add permissions...

* Cleaner permissions

* Apply permissions to ReviewLog endpoints

* Preprint permissions...

* Use reviews permissions on all preprint endpoints

* Filter preprint providers by reviews permission

* Add provider setup endpoint

* Allow node admins to see logs on their submissions

Assuming the provider settings allow it

* Hide log creators if comments are anonymous

* Add script to generate fake ReviewLogs

* Replace log state fields with action

* Creating review logs to trigger state transitions

* Allow resubmitting

* Fix admin

* Filter permissions correctly

* Responding to review

* Fix reviews migration

* Responding to review

* Fix stupid mistakes

* Cleaner PermissionHelper

* Fix provider log list view

* Add preprint list permissions tests

* Fix migrations after rebase

* Fix a couple things...

* No reviews workflow is None instead of 'none'

* Create review logs at /v2/reviews/review_logs/

* Add read-only title and contributors to preprints

* Hide preprints with private nodes from moderators

* Add date_last_transitioned field to preprints

* Expose live reload port for reviews

* Better permission names

* Tests for creating review logs, transitions

* Expose date_last_transitioned in API (CenterForOpenScience#27)

* [MOD-61] Set reviews settings by PATCHing provider (CenterForOpenScience#28)

* Nullable provider settings, set all at once (CenterForOpenScience#29)

* Add related counts for reviewables (CenterForOpenScience#30)

* Add related counts for reviewables

* Fixes

* CR

* [Fix] Hack around GuidQuerySet (CenterForOpenScience#31)

* [Feature] More API tests and some cleanup (CenterForOpenScience#32)

* More reviews tests

* plac8 flake8

* Filter providers by multiple permissions with OR

* Tests and cleanup...

* More cleanup

* Responding to review

* Make travis happy

* Post-rebase cleanup

* Remove osf-reviews campaign

* Make travis happier...

* Add can_view_reviews to /users/me/ (#1)

* [Feature][MOD-73] Rename log to action (#2)

* ReviewLog => Action

* Move Action model to osf.models

* Fix up migrations

* Fix failing tests

* Fix up migrations again

* Make /actions/ useful in the browsable API

* Cleanup for consistency

* Add `action` relationship to users serializer

* Comment out reviews-specific dev docker stuff

* Responding to review

* Remove OSFReviews from navbar (#3)

* Fix provider detail endpoint

* Responding to review...

* Keep permissions in permissions classes

* Move scripts to management commands

* Revert "Keep permissions in permissions classes"

This reverts commit 51d8b4d.

* Reenable sorting tests

* Permissions in permission classes, mark 2

* Remove Reviews from the nav bar

* Add email notifications

* undo docker change

* replace review_log with action

* Fix style

* Update test_notifications

* Apply requested changes

* Add tests

* remove extra line

* Change wording

* Use preprint_word for providers

* Apply requested changes

* Remove print and fix space

* Apply requested changes

* Update test

* Apply requested changes

* Update test
…#8)

* DOI minting

* Remove white space

* Remove check

* Apply requested changes

* Remove blank line

* Update test

* Apply requested changes

* Apply requested changes
* Add migration for reviews subscriptions

* Use iterator instead of paginator
aaxelb and others added 10 commits October 4, 2017 09:20
CenterForOpenScience#7769)

* Add html version of emails

Increase message max_length

* Fix test
…CenterForOpenScience#7767)

* Put all published preprints in `accepted` state

* Fix failing tests

* Fix migration conflict

* Fix failing test

* Fix failing test
…nce#7787)

* Update email templates

* * replace <br> with <p>
* use provider domain

* Apply requested changes
…terForOpenScience#7793)

* Allow filtering preprints by the node's is_public

Always filter out actions on preprints with private nodes
from `/users/me/actions/`

* Fix failing test

* Clearer names

* Fix typo
…nterForOpenScience#7778)

* Update email notification for new submission

* update test

* Fix flake8 error

* Apply requested change

* apply another request

* Update tests
@aaxelb

aaxelb commented Oct 12, 2017

Copy link
Copy Markdown
Collaborator Author

Closed in favor of #7807

@aaxelb aaxelb closed this Oct 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants